package com.adopapa.ethexplorer.contoller;

import java.math.BigInteger;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.web3j.crypto.Hash;

import com.adopapa.ethexplorer.domain.UserInfo;
import com.adopapa.ethexplorer.domain.UserInfo.UserType;
import com.adopapa.ethexplorer.domain.WalletInfo;
import com.adopapa.ethexplorer.service.ChaindataService;
import com.adopapa.ethexplorer.service.DataSystemService;
import com.adopapa.ethexplorer.service.UserInfoService;
import com.adopapa.ethexplorer.service.WalletManager;
import com.adopapa.ethexplorer.utils.Tools;

import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/api/v1/userinfo")
public class UserInfoController extends BaseController {

	@Autowired
	private ChaindataService chaindataService;
	@Autowired
	private UserInfoService userInfoService;
	@Autowired
	private DataSystemService dataSystemService;

	@GetMapping(path = "/userinfos/{pageNo}", produces = MediaType.APPLICATION_JSON_VALUE)
	public Mono<Map<String, Object>> getUserInfoPage(@RequestHeader String authorization, //
			@PathVariable Integer pageNo, @RequestParam(required = false) Integer pageSize, //
			@RequestParam(required = false) String userType, //
			@RequestParam(required = false) String userName) {

		checkAdmin(authorization);

		pageSize = Tools.empty(pageSize) ? 10 : pageSize;

		UserInfo userInfo = new UserInfo().userName(userName);
		if (!Tools.empty(userType)) {
			try {
				userInfo.setUserType(UserType.valueOf(userType));
			} catch (Exception e) {
				logger.error(ExceptionUtils.getStackTrace(e));
			}
		}

		Page<UserInfo> page = userInfoService.getUserInfoPage(pageNo, pageSize, userInfo);

		return Mono.just(ok(page));
	}

	@GetMapping(path = "/userinfo/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
	public Mono<Map<String, Object>> getUserInfo(@RequestHeader String authorization, @PathVariable String id) {
		UserInfo userInfo = userInfoService.getUserInfoById(id);
		if (Tools.empty(userInfo)) {
			throw new RuntimeException("用户信息不存在");
		}

		return Mono.just(ok(userInfo));
	}

	/**
	 * 管理员创建用户与钱包
	 */
	@PostMapping(path = "/userinfos", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
	public Mono<Map<String, Object>> saveUserInfo(@RequestHeader String authorization,
			@RequestBody Map<String, Object> params) {

		checkAdmin(authorization);

		String userName = getMapValue(params, "userName", String.class);
		String loginName = getMapValue(params, "loginName", String.class);
		String loginPwd = getMapValue(params, "loginPwd", String.class);
		String note = getMapValue(params, "note", String.class);
		String userType = getMapValue(params, "userType", String.class);

		String alias = getMapValue(params, "alias", String.class);
		Boolean wallet = getMapValue(params, "wallet", Boolean.class);
		String password = getMapValue(params, "password", String.class);

		if (Tools.empty(userName) || Tools.empty(loginName) || Tools.empty(loginPwd) || Tools.empty(password)) {
			throw new RuntimeException("用户名称、登录名称、登录密码及钱包密码不能为空");
		}

		UserInfo existed = userInfoService.getUserInfoByLoginName(loginName);
		if (!Tools.empty(existed)) {
			throw new RuntimeException("登录名称已被使用");
		}

		wallet = Boolean.TRUE;

		UserInfo userInfo = new UserInfo().userName(userName).loginName(loginName)//
				.loginPwd(Hash.sm3(loginPwd))//
				.createTime(Instant.now()).updateTime(Instant.now()).note(note);

		try {
			userInfo.setUserType(UserType.valueOf(userType));
		} catch (Exception e) {
			userInfo.setUserType(UserType.OPERATOR);
		}

		if (wallet == Boolean.TRUE) {
			try {
				Map<String, String> walletMap = WalletManager.createStandardWallet(password);
				WalletInfo walletInfo = new WalletInfo().address(walletMap.get("address")).alias(alias)
						.keystore(walletMap.get("keystore")).createTime(Instant.now())
						.password(Base64.getEncoder().encodeToString(password.getBytes())).alias(userName);
				
				userInfo.setWalletInfo(walletInfo);

			} catch (Exception e) {
				logger.error(ExceptionUtils.getStackTrace(e));
			}
		}

		dataSystemService.newUserInfo(userInfo);

		return Mono.just(ok());

	}

	/**
	 * 用户创建本人钱包
	 */
	@PostMapping(path = "/wallet", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
	public Mono<Map<String, Object>> createWallet(@RequestHeader String authorization,
			@RequestBody Map<String, String> params) {

		UserInfo userInfo = getUserInfo(authorization);

		String password = params.get("password");
		String alias = params.get("alias");

		if (Tools.empty(password)) {
			return Mono.just(other("password is empty"));
		}
		try {
			Map<String, String> walletMap = WalletManager.createStandardWallet(password);
			WalletInfo walletInfo = new WalletInfo().address(walletMap.get("address")).alias(alias)
					.keystore(walletMap.get("keystore")).createTime(Instant.now())
					.password(Base64.getEncoder().encodeToString(password.getBytes()));
			userInfo.setWalletInfo(walletInfo);

			dataSystemService.newUserInfo(userInfo);

			return Mono.just(ok());
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}
	
	/**
	 * 查询本人余额
	 */
	@GetMapping(path = "/balance", produces = MediaType.APPLICATION_JSON_VALUE)
	public Mono<Map<String, Object>> getBalance(@RequestHeader String authorization) {
		UserInfo userInfo = getUserInfo(authorization);
		BigInteger balance = chaindataService.getBalance(userInfo.getWalletInfo().getAddress());

		Map<String, Object> result = new HashMap<String, Object>();
		result.put("address", userInfo.getWalletInfo().getAddress());
		result.put("balance", balance.toString(10));

		return Mono.just(ok(result));

	}
	

}
